using AvaloniaMaidVSIX.Avalonia;
using AvaloniaMaidVSIX.Helpers;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO;
using System.Linq;
using System.Text;
using Task = System.Threading.Tasks.Task;

namespace AvaloniaMaidVSIX.Commands
{
    /// <summary>
    /// Command handler
    /// </summary>
    internal sealed class ExtractModuleCommand
    {
        /// <summary>
        /// Command ID.
        /// </summary>
        public const int CommandId = 4129;

        /// <summary>
        /// Command menu group (command set GUID).
        /// </summary>
        public static readonly Guid CommandSet = new Guid("c9e26580-831a-47a3-aea0-78cbf1d6e7eb");

        /// <summary>
        /// VS Package that provides this command, not null.
        /// </summary>
        private readonly AsyncPackage package;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExtractModuleCommand"/> class.
        /// Adds our command handlers for menu (commands must exist in the command table file)
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        /// <param name="commandService">Command service to add command to, not null.</param>
        private ExtractModuleCommand(AsyncPackage package, OleMenuCommandService commandService)
        {
            this.package = package ?? throw new ArgumentNullException(nameof(package));
            commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));

            var menuCommandID = new CommandID(CommandSet, CommandId);
            var menuItem = new MenuCommand(this.Execute, menuCommandID);
            commandService.AddCommand(menuItem);
        }

        /// <summary>
        /// Gets the instance of the command.
        /// </summary>
        public static ExtractModuleCommand Instance
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the service provider from the owner package.
        /// </summary>
        private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider
        {
            get
            {
                return this.package;
            }
        }

        /// <summary>
        /// Initializes the singleton instance of the command.
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        public static async Task InitializeAsync(AsyncPackage package)
        {
            // Switch to the main thread - the call to AddCommand in ExtractModuleCommand's constructor requires
            // the UI thread.
            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);

            OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
            Instance = new ExtractModuleCommand(package, commandService);
        }

        /// <summary>
        /// This function is the callback used to execute the command when the menu item is clicked.
        /// See the constructor to see how the menu item is associated with this function using
        /// OleMenuCommandService service and MenuCommand class.
        /// </summary>
        /// <param name="sender">Event sender.</param>
        /// <param name="e">Event args.</param>
        private void Execute(object sender, EventArgs e)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            var dte = ServiceProvider.GetServiceAsync(typeof(DTE)).GetAwaiter().GetResult() as DTE2;

            var info = VSHelper.GetSelectionItemInfo(dte);

            var file = info.ItemFullPath;

            if (file.EndsWith("xaml") == false) return;

            var text = File.ReadAllText(file);
            if (string.IsNullOrWhiteSpace(text)) return;
            var selectionText = info.SelectionText;

            var isValidBlock = XmlHelper.IsValidBlock(selectionText);

            if (isValidBlock == false)
            {
                VsShellUtilities.ShowMessageBox(package, "当前 xml 段不完整或者不合法！", "遭遇异常", OLEMSGICON.OLEMSGICON_CRITICAL, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
                return;
            }

            if (InputBoxHelper.Show("请输入文件名", "请输入文件名", "NewView", out var componentName))
            {
                var xamlInfo = XamlInfo.Parse(text);

                //var processedhostCode = ExtractModule.GetProcessedHostCode(text, selectionStartIndex, selectionEndIndex);
                var processedXamlPartCode = GetProcessedXamlPartCode(text, xamlInfo, selectionText, componentName);
                var processedCodePartCode = GetProcessedCodePartCode(xamlInfo, componentName);

                var xamlPath = GetXamlPath(file, componentName);
                var codePath = GetCodePath(file, componentName);

                //File.WriteAllText(file, processedhostCode);

                var localNamespaceName = xamlInfo.GetNamespaceFromClassName();
                var localNamespaceShortName = NamespaceHelper.GetNamespaceShortName(localNamespaceName);

                info.UpdateSelection($"<{localNamespaceShortName}:{componentName}/>");
                File.WriteAllText(xamlPath, processedXamlPartCode);
                File.WriteAllText(codePath, processedCodePartCode);

                VsShellUtilities.OpenDocument(package, xamlPath);
                VsShellUtilities.OpenDocument(package, file);

                var currentNamespace = xamlInfo.Namespaces.FirstOrDefault(i => i.ShortName == localNamespaceName);
                if (currentNamespace is null)
                {
                    using (var document = new AxamlDocument(dte, file))
                    {
                        document.InsertXmlns(new UsingPair() { ShortName = localNamespaceShortName, NamespaceName = localNamespaceName }.ToString());
                    }

                }

            }

        }


        #region impls

        public class UsingPair
        {
            public string ShortName { get; set; }
            public string NamespaceName { get; set; }

            public override string ToString()
            {
                return $"xmlns:{ShortName}=\"using:{NamespaceName}\"";
            }
        }

        public class XamlInfo
        {
            public List<UsingPair> Namespaces { get; set; } = new List<UsingPair>();
            public string DataType { get; set; }
            public string ClassName { get; set; }

            public string GetNamespaceFromClassName()
            {
                var lastDot = ClassName.LastIndexOf('.');
                return ClassName.Substring(0, lastDot);
            }

            public static XamlInfo Parse(string text)
            {
                var usings = GetUsings(text);

                var dataType = XmlHelper.GetAttributeBeforeSpecialToken(text, "x:DataType", ">");
                var className = XmlHelper.GetAttributeBeforeSpecialToken(text, "x:Class", ">");

                return new XamlInfo()
                {
                    DataType = dataType,
                    Namespaces = usings,
                    ClassName = className,
                };
            }

            private static List<UsingPair> GetUsings(string text)
            {
                var startIndex = 0;
                var namespaceNameList = new List<UsingPair>();
                while (startIndex >= 0)
                {
                    var usingPair = GetUsing(text, startIndex, out var nextStartIndex);
                    startIndex = nextStartIndex;
                    if (usingPair is null) break;
                    namespaceNameList.Add(usingPair);
                }
                return namespaceNameList;
            }

            private static UsingPair GetUsing(string text, int startIndex, out int nextStartIndex)
            {
                nextStartIndex = -1;
                const string UsingToken = "=\"using:";
                const string CloseToken = "\"";
                const string XmlnsToken = "xmlns:";
                var openIndex = text.IndexOf(UsingToken, startIndex);

                if (openIndex < 0) return null;

                var closeIndex = text.IndexOf(CloseToken, openIndex + UsingToken.Length);

                if (closeIndex < 0) return null;

                var xmlnsIndex = text.LastIndexOf(XmlnsToken, openIndex);

                if (xmlnsIndex < 0) return null;

                var shortName = text.Substring(xmlnsIndex + XmlnsToken.Length, openIndex - xmlnsIndex - XmlnsToken.Length);
                var namespaceName = text.Substring(openIndex + UsingToken.Length, closeIndex - openIndex - UsingToken.Length);

                nextStartIndex = closeIndex + 1;

                return new UsingPair() { NamespaceName = namespaceName, ShortName = shortName };
            }

        }

        public static string GetCodePath(string hostFile, string componentName)
        {
            var dir = Path.GetDirectoryName(hostFile);
            var fileName = string.Empty;
            if (hostFile.EndsWith(".axaml")) fileName = componentName + ".axaml.cs";
            else if (hostFile.EndsWith(".xaml")) fileName = componentName + ".xaml.cs";
            else throw new Exception();

            return Path.Combine(dir, fileName);
        }
        public static string GetXamlPath(string hostFile, string componentName)
        {
            var dir = Path.GetDirectoryName(hostFile);
            var fileName = string.Empty;
            if (hostFile.EndsWith(".axaml")) fileName = componentName + ".axaml";
            else if (hostFile.EndsWith(".xaml")) fileName = componentName + ".xaml";
            else throw new Exception();

            return Path.Combine(dir, fileName);
        }

        public static string GetProcessedHostCode(string hostCode, int selectionStartIndex, int selectionEndIndex)
        {
            var part1 = hostCode.Substring(0, selectionEndIndex);
            var part2 = hostCode.Substring(selectionEndIndex + 1);
            return part1 + part2;
        }

        public static string GetProcessedXamlPartCode(string hostCode, XamlInfo xamlInfo, string selectionText, string componentName)
        {
            var sb = new StringBuilder();
            var localNamespaceName = xamlInfo.GetNamespaceFromClassName();

            sb.AppendLine($@"<UserControl
    x:Class=""{localNamespaceName}.{componentName}""
    xmlns=""https://github.com/avaloniaui""
    xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
    xmlns:d=""http://schemas.microsoft.com/expression/blend/2008""
    xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006""");

            foreach (var namespaceUsing in xamlInfo.Namespaces)
            {
                sb.AppendLine(namespaceUsing.ToString());
            }

            var localNamespaceShortName = NamespaceHelper.GetNamespaceShortName(localNamespaceName);

            if (xamlInfo.Namespaces.Any(i => i.ShortName == localNamespaceShortName) == false)
            {
                sb.AppendLine(new UsingPair() { NamespaceName = localNamespaceName, ShortName = localNamespaceShortName }.ToString());
            }

            sb.AppendLine(@"    d:DesignHeight=""450""
    d:DesignWidth=""800""");

            if (string.IsNullOrWhiteSpace(xamlInfo.DataType) == false)
                sb.AppendLine($@"    x:DataType=""{xamlInfo.DataType}""");

            sb.AppendLine(@"    mc:Ignorable=""d"">");

            sb.AppendLine();

            sb.AppendLine(selectionText);

            sb.AppendLine();

            sb.AppendLine(@"</UserControl>");
            return sb.ToString();
        }

        public static string GetProcessedCodePartCode(XamlInfo xamlInfo, string componentName)
        {
            var localNamespaceName = xamlInfo.GetNamespaceFromClassName();

            var code = $@"using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace {localNamespaceName};

public partial class {componentName} : UserControl
{{
    public {componentName}()
    {{
        InitializeComponent();
    }}
}}";
            return code;
        }

        #endregion
    }
}
